package top.v5it.japi.plus.core.spring.web;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import top.v5it.japi.plus.core.util.JsoupUtil;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Objects;
import java.util.Optional;

/**
 * xss过虑
 *
 * @author zhanpu
 * @date 20-12-24
 */
@Slf4j
public class JapiHttpServletRequestWrapper extends HttpServletRequestWrapper {

    /**
     * db操作关键字
     */
    private static final String[] SQL_KEYWORDS = {
            "master"
            , "truncate"
            , "insert"
            , "select"
            , "delete"
            , "update"
            , "declare"
            , "alter"
            , "drop"
            , "sleep"
    };

    /**
     * sql 替换字符
     */
    private static final String REPLACE_STR = "";

    /**
     * 空白
     */
    private static final String BLANK = " ";

    /**
     * 原始HttpServletRequest
     */
    final HttpServletRequest orgRequest;

    /**
     * 是否过虑富文件内容
     */
    final boolean isIncludeRichText;

    /**
     * 缓存的body信息
     */
    private final String body;

    private static final String[] CONTENT_TYPE_ARR = new String[]{MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_XML_VALUE};

    /**
     * 构造函数
     *
     * @param request           {@link HttpServletRequest}
     * @param isIncludeRichText true-过虑，false-不过虑
     */
    JapiHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) throws IOException {
        super(request);
        this.orgRequest = request;
        this.isIncludeRichText = isIncludeRichText;

        // 此处不能关闭流，不然到达controller层无法获取数据
        this.body = Objects.isNull(request.getInputStream()) ? "" : StrUtil.str(IoUtil.readBytes(request.getInputStream(), false), request.getCharacterEncoding());
    }

    /**
     * 获取最原始的request的静态方法
     *
     * @return
     */
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
        if (req instanceof JapiHttpServletRequestWrapper) {
            return ((JapiHttpServletRequestWrapper) req).getOrgRequest();
        }

        return req;
    }

    /**
     * 获取最原始的request
     *
     * @return
     */
    HttpServletRequest getOrgRequest() {
        return orgRequest;
    }

    /**
     * 清除sql关键字
     *
     * @param value
     * @return
     */
    private String cleanSqlKeyWords(String value) {

        String paramValue = value;

        for (String keyword : SQL_KEYWORDS) {

            boolean matched = StrUtil.length(paramValue) > keyword.length() + 4
                    && StrUtil.containsAnyIgnoreCase(paramValue, BLANK + keyword, keyword + BLANK, BLANK + keyword + BLANK);

            if (matched) {
                paramValue = StrUtil.replace(paramValue, keyword, REPLACE_STR);
            }
        }

        return paramValue;
    }

    /**
     * 重写getReader
     *
     * @return
     * @throws IOException
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    /**
     * 获取body信息
     *
     * @return
     */
    public String getBody() {
        return this.body;
    }

    /**
     * 是否可接受的content-type请求，包含(json/xml/text)
     */
    boolean isAcceptContentTypeRequest() {
        String contentType = super.getHeader(HttpHeaders.CONTENT_TYPE);
        return !StrUtil.containsAnyIgnoreCase(contentType, CONTENT_TYPE_ARR);
    }

    /**
     * 去掉带有特殊字符
     *
     * @param content
     * @return
     */
    String clean(String content) {
        return
                Optional.ofNullable(content)
                        .filter(StrUtil::isNotBlank)
                        .map(JsoupUtil::clean)
                        .map(this::cleanSqlKeyWords)
                        .orElse(content);
    }

    static final class JapiServletInputStream extends ServletInputStream {

        private final InputStream in;

        JapiServletInputStream(InputStream in) {
            this.in = in;
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }

        @Override
        public int read() throws IOException {
            return in.read();
        }

    }
}
